package com.ikas.ai.utils;

import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.mutable.MutableInt;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.*;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/***
 * Simply Redis Util - Don't be new
 */
@SuppressWarnings("all")
@Slf4j
public final class RedisUtils {

    private RedisUtils() {
    }

    private static RedisTemplate<String, Object> redis;
    private static ValueOperations<String, Object> string;
    private static HashOperations<String, String, Object> hash;
    private static ListOperations<String, Object> list;
    private static SetOperations<String, Object> set;
    private static ZSetOperations<String, Object> zset;
    private static RedissonClient redisson;

    public static void setRedis(RedisTemplate<String, Object> redis) {
        RedisUtils.redis = redis;
        RedisUtils.string = redis.opsForValue();
        RedisUtils.hash = redis.opsForHash();
        RedisUtils.list = redis.opsForList();
        RedisUtils.set = redis.opsForSet();
        RedisUtils.zset = redis.opsForZSet();
    }
    public static void setRedisson(RedissonClient redisson) {
        RedisUtils.redisson = redisson;
    }

    private static <T> T ifNumber(Class<T> t, Object value) {
        if (!Number.class.isInstance(value)) {
            return (T) value;
        }

        if (t == Double.class) {
            return (T) Double.valueOf(value.toString());
        }
        if (t == Long.class) {
            return (T) Long.valueOf(value.toString());
        }
        if (t == Integer.class) {
            return (T) Integer.valueOf(value.toString());
        }
        if (t == Float.class) {
            return (T) Float.valueOf(value.toString());
        }
        return (T) value;
    }

    /**
     * 获取redis保存的值
     *
     * @param key key
     * @return T 泛型值，强转可能失败
     * @author ly
     */
    public static <T> T get(String key) {
        Object value = string.get(key);
        return value == null ? null : (T) value;
    }

    /**
     * 获取多个key对应的value
     *
     * @param keys
     * @return
     */
    public static <T> java.util.List<T> multiGet(java.util.List<String> keys) {
        java.util.List<T> list = new ArrayList<>();
        string.multiGet(keys).forEach(i -> {list.add((T)i);});
        return list;
    }


    /**
     * 获取多个key对应的value
     *
     * @param keys
     * @return
     */
    public static <T> void multiPut(Map<String, T> map) {
        string.multiSet(map);
    }

    /**
     * 获取redis保存的值
     *
     * @param key key
     * @return T 泛型值，强转可能失败
     * @author ly
     */
    public static Long getLong(String key) {
        Object value = string.get(key);
        return value == null ? null : value instanceof Integer ? ((Integer) value).longValue() : (Long) value;
    }

    /**
     * 设置有过期时间的key
     *
     * @param key   key
     * @param value value
     * @author ly
     */
    public static <T> void set(String key, T value) {
        string.set(key, value);
    }

    /**
     * 设置有过期时间的key
     *
     * @param key    key
     * @param value  value
     * @param expire expire
     * @param unit   timeUnit
     * @author ly
     */
    public static <T> void set(String key, T value, long expire, TimeUnit unit) {
        string.set(key, value, expire, unit);
    }

    /**
     * 设置有过期时间的key
     *
     * @param key   key
     * @param value value
     * @author ly
     */
    public static <T> void setIfPresent(String key, T value) {
        string.setIfPresent(key, value);
    }

    /**
     * 删除redis中的key
     *
     * @param keys keys
     * @author ly
     */
    public static void del(String... keys) {
        if (keys == null || keys.length < 1) {
            return;
        }

        if (keys.length == 1) {
            redis.delete(keys[0]);
            return;
        }

        redis.delete(Arrays.stream(keys).collect(Collectors.toSet()));
    }

    /**
     * 删除redis中的key
     *
     * @param keys keys
     * @author ly
     */
    public static void del(Collection<String> keys) {
        if (keys == null || keys.isEmpty()) {
            return;
        }

        redis.delete(keys);
    }

    public static java.util.List<String> getKeysByPrefix(String prefix) {
        java.util.List<String> keys = new ArrayList<>();
        ScanOptions options = ScanOptions.scanOptions().match(prefix + "*").count(10000L).build();
        Cursor<byte[]> cursor = redis.getConnectionFactory().getConnection().scan(options);
        while (cursor.hasNext()) {
            keys.add(new String(cursor.next()));
        }
        return keys;
    }

    /**
     * 自增操作
     *
     * @param key key
     * @return long 自增后的值
     * @author ly
     */
    public static long inc(String key) {
        return inc(key, 1);
    }

    /**
     * 自增操作
     *
     * @param key key
     * @return long 自增后的值
     * @author ly
     */
    public static long inc(String key, long inc) {
        return Optional.ofNullable(string.increment(key, inc)).orElse(0L);
    }

    /***
     * 是否存在该Key
     * @param key
     * @return
     */
    public boolean haskey(String key) {
        return redis.hasKey(key);
    }

    /**
     * 缓存查询模板
     *
     * @param cacheSelector    查询缓存的方法
     * @param databaseSelector 数据库查询方法
     * @return T
     */
    public static <T> T selectFromCacheOrDb(String key, Supplier<T> databaseSelector) {
        return selectFromCacheOrDb(key, databaseSelector, true);
    }
    /**
     * 缓存查询模板
     *
     * @param cacheSelector    查询缓存的方法
     * @param useDbValToCache    是否重新放入redis
     * @param databaseSelector 数据库查询方法
     * @return T
     */
    public static <T> T selectFromCacheOrDb(String key, Supplier<T> databaseSelector, Boolean useDbValToCache) {
        try {
            log.debug("query data from redis ······");
            // 先查 Redis缓存
            T cacheVal = get(key);
            if (cacheVal == null) {
                // 查询数据库并放入换成中
                log.debug("query data from db ······");
                T dbVal = databaseSelector.get();
                if (Boolean.TRUE.equals(useDbValToCache) && dbVal != null) {
                    log.debug("query data from db and reSet to Redis······");
                    set(key, dbVal);
                }
                return dbVal;
            } else {
                return cacheVal;
            }
        } catch (Exception e) {
            // 缓存查询出错，则去数据库查询
            log.error("redis error：", e);
            log.debug("query data from database ······");
            return databaseSelector.get();
        }
    }

    /**
     * Lock operation - 锁操作
     */
    public static abstract class Lock {
        private Lock() {
        }

        /**
         * 获取锁
         *
         * @param key key
         */
        public static RLock get(String key) {
            return redisson.getLock(key);
        }

        /**
         * 加锁执行逻辑
         *
         * @param key      锁key
         * @param runnable 运行逻辑
         * @author ly
         */
        public static void lock(String key, Runnable runnable, Supplier<RuntimeException> lockError) {
            // try lock
            RLock lock = get(key);
            try {
                if (!lock.tryLock(3, TimeUnit.SECONDS)) {
                    log.warn("Try to lock error, key is {}", key);
                    if (lockError != null) {
                        throw lockError.get();
                    }
                }
                // do something
                runnable.run();
            } catch (InterruptedException e) {
                log.warn("Lock error, key is {}", key, e);
            } finally {
                lock.unlock();
            }
        }

        /**
         * 加锁方法 --- Double check
         *
         * @param key    key
         * @param test   判断函数, 返回null表示需加锁
         * @param ifLock 需要加锁的操作函数, 执行点为判断失败如果加锁成功后又判断失败，则执行函数后返回
         * @param value  需要加锁的值
         * @return R is nullable
         */
        public static <T, R> R doublecheck(String key, Function<T, R> test, Function<T, R> ifLock, T value) {
            // first check
            R result = test.apply(value);
            if (result != null) {
                return result;
            }

            // try lock
            RLock lock = get(key);
            try {
                if (!lock.tryLock(3, TimeUnit.SECONDS)) {
                    return null;
                }
                // double check
                result = test.apply(value);
                if (result != null) {
                    return result;
                }

                return ifLock.apply(value);
            } catch (InterruptedException e) {
                log.warn("try to lock error, key is {}, value is {}", key, value);
            } finally {
                lock.unlock();
            }

            // if lock error
            return null;
        }
    }


    /**
     * Set operation - Set操作
     */
    public static final class Set {


        /**
         * Set.add
         *
         * @param key 哪一个map
         * @param t   泛型类
         * @author ly
         */
        public static <T> void add(String key, T value) {
            set.add(key, value);
        }


        /**
         * Set.size
         *
         * @param key 哪一个map
         * @param t   泛型类
         * @author ly
         */
        public static <T> long size(String key, T value) {
            return Optional.ofNullable(set.size(key)).orElse(0L);
        }

        /**
         * 获取所有的成员
         *
         * @param key
         * @param <T>
         * @return
         */
        public static <T> java.util.Set<T> members(String key, Class<T> clazz) {
            java.util.Set<Object> result = Optional.ofNullable(set.members(key)).orElse(Sets.newHashSet());
            return result.stream().map(src -> {
                T t = null;
                if (clazz == String.class) {
                    t = (T) src.toString();
                } else if (clazz == Byte.class) {
                    t = (T) Byte.valueOf(src.toString());
                } else if (clazz == Short.class) {
                    t = (T) Short.valueOf(src.toString());
                } else if (clazz == Integer.class) {
                    t = (T) Integer.valueOf(src.toString());
                } else if (clazz == Long.class) {
                    t = (T) Long.valueOf(src.toString());
                } else if (clazz == Boolean.class) {
                    t = (T) Boolean.valueOf(src.toString());
                } else {
                    t = (T) JSON.parseObject(src.toString(), clazz);
                }
                return t;
            }).collect(Collectors.toSet());
        }

        /**
         * 获取成员
         *
         * @param key
         * @return
         */
        public static java.util.Set<Object> members(String key) {
            return set.members(key);
        }

        /**
         * 添加集合
         *
         * @param key
         * @param all
         */
        public static <T> void addAll(String key, Collection<T> all) {
            set.add(key, all.toArray());
        }

        /**
         * 删除 元素
         *
         * @param key
         * @param member
         * @param <T>
         * @return
         */
        public static <T> long remove(String key, T member) {
            return set.remove(key, member);
        }
    }


    /**
     * Hash operation - Map操作
     */
    public static final class Hash {
        private Hash() {
        }

        /**
         * Map.inc
         *
         * @param key 哪一个map
         * @param t   泛型类
         * @author ly
         */
        public static double inc(String key, String member, double inc) {
            return hash.increment(key, member, inc);
        }

        /**
         * Map.getAll
         *
         * @param key 哪一个map
         * @param t   泛型类
         * @author ly
         */
        public static <T> Map<String, T> all(String key, Class<T> t) {
            boolean needNumber = Number.class.isAssignableFrom(t);
            return hash.entries(key).entrySet().stream()
                    .filter(e -> e.getValue() != null && (t.isInstance(e.getValue()) || needNumber && Number.class.isInstance(e.getValue())))
                    .collect(Collectors.toMap(Map.Entry::getKey, e -> ifNumber(t, e.getValue()), (v1, v2) -> v2));
        }

        /**
         * Map.get
         *
         * @param key    哪一个map
         * @param member map中的key
         * @author ly
         */
        public static <T> T get(String key, String member, Class<T> t) {
            Object value = hash.get(key, member);
            return ifLong(t, value);
        }

        /**
         * Map.get
         *
         * @param key    哪一个map
         * @param member map中的key
         * @author ly
         */
        public static <T> Map<String, T> get(String key, Collection<String> members) {
            if (CollUtil.isEmpty(members)) {
                return Collections.emptyMap();
            }

            java.util.List<Object> value = hash.multiGet(key, members);
            if (CollUtil.isEmpty(value)) {
                return Collections.emptyMap();
            }

            MutableInt idx = new MutableInt();
            Map<String, T> map = new HashMap<>();
            members.forEach(m -> map.put(m, (T) value.get(idx.getAndIncrement())));
            return map;
        }

        /**
         * Map.get
         *
         * @param key    哪一个map
         * @param member map中的key
         * @author ly
         */
        public static <T> Map<String, T> get(String key, Collection<String> members, Class<T> t) {
            java.util.List<Object> value = hash.multiGet(key, members);
            if (CollUtil.isEmpty(value)) {
                return Collections.emptyMap();
            }

            MutableInt idx = new MutableInt();
            return members.stream().collect(Collectors.toMap(Function.identity(),
                    v -> ifNumber(t, value.get(idx.getAndIncrement())), (v1, v2) -> v2));
        }

        /**
         * 如果是获取Long, 但redis中的默认值为Integer
         *
         * @param t     泛型类
         * @param value 值
         * @return T 泛型
         * @author ly
         */
        private static <T> T ifLong(Class<T> t, Object value) {
            return value == null ? null :
                    t == Long.class ? (T) Long.valueOf(value.toString()) : t.isInstance(value) ? (T) value : null;
        }

        /**
         * Map.put
         *
         * @param key    哪一个map
         * @param member map中的key
         * @param value  map中的value
         * @author ly
         */
        public static <V> void put(String key, String member, V value) {
            hash.put(key, member, value);
        }

        /**
         * Map.putAll
         *
         * @param key 哪一个map
         * @param map 存入的map
         * @author ly
         */
        public static <V> void putAll(String key, Map<String, V> map) {
            hash.putAll(key, map);
        }

        /**
         * 删除map中的元素
         *
         * @param key     哪一个map
         * @param members map中的keys
         * @author ly
         */
        public static void del(String key, String... members) {
            if (members == null || members.length < 1) {
                return;
            }

            hash.delete(key, (Object[]) members);
        }
    }

    /**
     * 列表操作
     */
    public static final class List {

        /**
         * 获取所有元素
         *
         * @param key 贱命
         * @return 结果集
         */
        public static <T> java.util.List<T> all(String key) {
            return (java.util.List<T>) list.range(key, 0, -1);
        }

        /**
         * 存放元素
         *
         * @param key  键
         * @param data 值
         * @return 结果集
         */
        public static <T> void add(String key, Collection<T> data) {
            if (CollUtil.isEmpty(data)) {
                return;
            }

            list.rightPushAll(key, (Collection<Object>) data);
        }

        /**
         * 存放元素
         *
         * @param key  键
         * @param data 值
         * @return 结果集
         */
        public static <T> void add(String key, T data) {
            if (data == null) {
                return;
            }

            list.rightPush(key, data);
        }
    }
}
